package net.aiotos.driver;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import net.aiotos.common.dto.*;
import jdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.aiotos.common.exception.DataNotExistError;

@Slf4j
public abstract class IOTOSDriverI {
	@Getter
	@Setter
	@Deprecated
	private IOTOSys zm;
	@Getter
	private final String sysId;
	@Getter
	@Setter
	@Deprecated
	private Map<String, DataDTO> data2attrs;
	@Getter
	@Setter
	@Deprecated
	private Map<String, String[]> data2subs;
	@Getter
	@Setter
	@Deprecated
	private final Map<String, String> name2dataId;
	@Getter
	@Setter
	@Deprecated
	private Map<String,Object> memoryValue = new HashMap<>();
	@Getter
	protected final DeviceDTO deviceDTO;
	@Getter
	@Setter
	private boolean collectingOneCircle = false; // 20200114 by lrq 采集循环仅执行一次就退出，不循环采集，用户遍历一次点表！
	@Getter
	@Setter
	private boolean pauseCollect = true; // added by lrq 20200711 区别于全局zmiot中的pause_collect，为每个驱动实例都单独加上采集控制，避免某一个设备上线启动采集，导致全局让所有设备驱动都执行采集，包括未上线的设备！

	@Deprecated
	final protected DeviceDTO sysAttrs;
	/**
	 * 驱动逻辑处理
	 * @param deviceDTO
	 */
	public IOTOSDriverI(@NonNull final DeviceDTO deviceDTO) {
		this.sysId = deviceDTO.getDeviceOid();
		this.sysAttrs = this.deviceDTO = deviceDTO;
		data2attrs = deviceDTO.getData();
		name2dataId = new HashMap<String, String>();
		for (Map.Entry<String, DataDTO> entry : data2attrs.entrySet()) {
			String dataOid = entry.getKey();
			DataDTO dto = entry.getValue();
			name2dataId.put(dto.getName(), dataOid);
		}

		DeviceConfigDTO deviceDTOConfig = deviceDTO.getConfig(DeviceConfigDTO.class);

	}

	/**
	 * 用户私有业务逻辑处理
	 * 传入的attrs是点表补全后，当前设备下的部分，包括数据点
	 */
	public abstract void InitComm() throws Exception;

	public void connectEvent(boolean state) throws Exception {
		online(state);
		if(state) {
			log.warn("device connected.");
			pauseCollect = false;
		} else {
			log.warn("device disconnected.");
			pauseCollect = true;
		}
	}

	public final DataDTO getDataDTOWithName(@NonNull String dataName) {
		if(name2dataId.containsKey(dataName)) {
			return getDataDTO(name2dataId.get(dataName));
		}
		throw new DataNotExistError(dataName);
	}

	public final DataDTO getDataDTO(@NonNull String dataOid) {
		if(deviceDTO.getData().containsKey(dataOid)) {
			return deviceDTO.getData().get(dataOid);
		}
		throw new DataNotExistError(dataOid);
	}

	public final DataPubDTO dataPub(@NonNull String dataOid, @NonNull Object value) {
		DataDTO dataDTO = getDataDTO(dataOid);
		String pointId = String.format("%s.%s.%s", deviceDTO.getGatewayUuid(), deviceDTO.getDeviceOid(), dataDTO.getDataOid());
		return new DataPubDTO(pointId, value);
	}

	public final DataPubDTO dataPubWithName(@NonNull String dataName, @NonNull Object value) {
		DataDTO dataDTO = getDataDTOWithName(dataName);
		String pointId = String.format("%s.%s.%s", deviceDTO.getGatewayUuid(), deviceDTO.getDeviceOid(), dataDTO.getDataOid());
		return new DataPubDTO(pointId, value);
	}

	/**
	 * 获取设备配置信息
	 * @param tClass 因为设备配置用户可以根据自己的需要定义
	 * @param <T>
	 * @return
	 */
	public final <T> T getDeviceConfigDTO(@NonNull Class<T> tClass) {
		return deviceDTO.getConfig(tClass);
	}

	/**
	 * 获取数据点配置信息
	 * @param dataOid
	 * @param tClass 因为数据点配置用户可以根据自己的需要定义
	 * @param <T>
	 * @return
	 */
	public final <T> T getDataConfigDTO(String dataOid, Class<T> tClass) {
		return getDataDTO(dataOid).getConfig(tClass);
	}

	/**
	 * added by lrq 独立抽出本地发布
	 * @param id
	 * @param value
	 */
	@Deprecated
	protected void pubLocal(@NonNull String id, @NonNull String value) {
		// 待实现
		String pointId = pointId(id);
		// 类型不对，暂不实现
		String[] subIds = data2subs.get(id);
		if(subIds == null) {
			return;
		}
		for (String devId : subIds) {
			RunLocalPubThread t = new RunLocalPubThread(zm.iotoSysI, zm.driver(deviceDTO.getGatewayUuid() + "." + deviceDTO.getDeviceOid()), pointId, value);
			t.setDaemon(true);
			t.start();
		}
	}

	/**
	 * 认定数据点方式访问的，都是对当前设备下的，如果对其他设备的数据点进行访问，需要带上其设备id!
	 * @param dataId
	 * @return
	 */
	@Deprecated
	public String pointId (String dataId) {
		if (data2attrs.containsKey(dataId)) {
			return zm.iotoSysI.getUuid() + "." + sysId + "." + dataId;
		} else {
			throw new DataNotExistError(dataId);
		}
	}

	/**
	 * 将名称标识（方便人读和写）换成ID标识（保证唯一性，方便驱动执行可复用性），注意，是不包括接入点id的最多后两段
	 * @param name
	 * @return
	 */
	@Deprecated
	public String id(String name){
		String[] ids = name.split("\\.");
		String backValue = "";
		if (ids.length == 1) {
			return this.name2dataId.get(name);
		} else if (ids.length == 2) {
			for(Map.Entry<String, DeviceDTO> entry : zm.mdevAttrs.entrySet()) {
				String devid = entry.getKey();
				DeviceDTO attr = entry.getValue();
				if(ids[0].equals(attr.getName())) {
					backValue = devid.split("\\.")[1] + "." + zm.driver(devid).name2dataId.get(ids[1]);
					return backValue;
				}
			}
		} else if (ids.length == 3) {
			return name;
		}
		throw new DataNotExistError(name);
	}

	/**
	 * 将id标识换成名称标识，注意，是不包括接入点id的最多后两段
	 * @param id
	 * @return
	 */
	@Deprecated
	public String name(String id){
		String[] ids = id.split("\\.");
		if (ids.length == 1 && data2attrs.containsKey(id)) {
			return data2attrs.get(id).getName();
		} else if(ids.length == 2){
			try {
				return this.zm.mdevAttrs.get( this.zm.getUuid()+"."+ids[0]).getName()+"."+this.zm.mpointAttrs.get(this.zm.getUuid()+"."+id).getName();
			} catch (NullPointerException ex) {
				// 返回NULL，说明数据点不存在。
			}
		} else if(ids.length == 3){
			//log.error("id not valid!");
		}
		throw new DataNotExistError(id);
	}

	/**
	 * 合并PubMsg()、Event_syncPubMsg()以及Event_syncPubMsg()，注意，是不包括接入点id的最多后两段
	 * 默认接受name标识，而不是id标识，方便编写程序调试，到发布的时候再去参数中变量变成id，然后加上self.name()来变成名称
	 * @return
	 */
	public ResultDTO setValue(DataPubDTO data) throws Exception {
		String[] strings = data.getId().split(".");
		if(strings.length == 1) {
			return setValue(name(data.getId()), data.getValue().toString());
		} else if(strings.length == 3) {
			return setValue(name(strings[2]), data.getValue().toString());
		} else {
			throw new Exception("数据点参数错误:" + data.getId());
		}
	}

	public ResultDTO setValue(@NonNull String name, @NonNull String value) throws Exception {
  		String id = id(name);
		String[] ids = id.split("\\.");
		if(ids.length == 1){
			memoryValue.putIfAbsent(id, value);
			pubLocal(id, value);
			return zm.PubMsg(pointId(id), value);
		} else if(ids.length == 2){
			ResultDTO resultDTO = zm.mdev2driver.get(zm.getUuid()+ "." + ids[0]).Event_setData(ids[1], value);
			if( resultDTO.getCode() == ErrorEnum.Successfull){
				return zm.PubMsg(zm.getUuid() + "." + id, value);
			}
			return resultDTO;
		} else if (ids.length == 3) {
			return Event_syncPubMsg(id, value);
		} else {
			throw new Exception("数据点异常");
		}
	}

	/**
	 * 批量上报时，如果做到批量到云平台，同时支持本地发布订阅(Java暂不支持)
	 * @param values [{id, value}]
	 * @return List<ResultDTO> [{msg, code, data, id}]
	 */
	public List<ResultDTO> setValues(String values) throws Exception {
		return setValues(JSONArray.parseArray(values, DataPubDTO.class));
	}
	/**
	 * 批量上报时，如果做到批量到云平台，同时支持本地发布订阅(Java暂不支持)
	 * @param values List<DataPubDTO>
	 * @return List<ResultDTO> [{msg, code, data, id}]
	 */
	public List<ResultDTO> setValues(List<DataPubDTO> values) throws Exception {
		for (DataPubDTO value : values) {
			memoryValue.putIfAbsent(value.getId(), value.getValue());
		}
		return zm.PubMsgs(values);
	}

	/**
	 * 本地订阅的监测点暂不支持全局订阅派发（与当前V2.0平台订阅下默认都是全局订阅派发相反），注意，是不包括接入点id的最多后两段
	 * 合并GetDeviceData()、GetPlatformData()以及Event_getData
	 * 默认接受name标识，而不是id标识，方便编写程序调试，到发布的时候再去参数中变量变成id，然后加上self.name()来变成名称
	 * @return
	 */
	public String value(String name, String param , String source){

		String id = id(name);
		String[] ids = id.split("\\.");
		String pointId = "";
		if (ids.length == 1) {
			if(source.equalsIgnoreCase("device") || source.equals("d")) {
				ResultDTO resultDTO = Event_getData(id, param);
				DataDTO dataDTO = resultDTO.getData().toJavaObject(DataDTO.class);
				return dataDTO.getValue();
			} else if(source.equalsIgnoreCase("memory") || source.equals("m")) {
//				added by lrq20200114 当发现内存数据初始没有时，那么就转为去设备端获取！！
				if(memoryValue.containsKey(id)) {
					memoryValue.replace(id, value(name, param, "device"));
				}
				return memoryValue.get(id).toString();
			} else if(source.equalsIgnoreCase("platform") || source.equals("p")) {
				pointId = pointId(id);
			} else {
				throw new ValueException("类型不支持：" + source);
			}
		} else if (ids.length == 2) {
			if(source.equalsIgnoreCase("device") || source.equals("d")) {
				ResultDTO resultDTO =  Event_getData(ids[1], param);
				DataDTO dataDTO = resultDTO.getData().toJavaObject(DataDTO.class);
				return dataDTO.getValue();
			} else if(source.equalsIgnoreCase("memory") || source.equals("m")) {
				return memoryValue.containsKey(id) ? memoryValue.get(id).toString() : null;
			} else if(source.equalsIgnoreCase("platform") || source.equals("p")) {
				pointId = zm.getUuid() + '.' + id;
			} else {
				throw new ValueException("类型不支持：" + source);
			}
		} else if (ids.length == 3) {
			if(source.equalsIgnoreCase("device") || source.equals("d")) {
				ResultDTO resultDTO = zm.iotoSysI.getDeviceData(id, param);
				if(resultDTO.getCode() != ErrorEnum.Successfull) {
					log.warn("id={}, source={}, ret={}", id, source, resultDTO);
					return null;
				} else {
					return resultDTO.getData().toJSONString();
				}
			}
		}
		ResultDTO result = zm.iotoSysI.getPlatformData(pointId, param);
		if(result.getCode() != ErrorEnum.Successfull) {
			log.warn("pointId={}, source={}, ret={}", id, source, result);
			return null;
		} else {
			DataDTO dataDTO = result.getData().toJavaObject(DataDTO.class);
			return dataDTO.getValue();
		}
	}

	/**
	 * #订阅了当前设备某个监测点的外部设备id，好在监测点变化时候找到他通知过去
	 * #【注意】本地订阅，当前是以整个设备来订阅，接下来会支持以监测点为单元来订阅
	 * @param dataId
	 * @return
	 */
	public String  sunbscribers(String dataId){
		return zm.mpoint2subs.get(pointId(dataId)).toString();
	}

	/**
	 * 设备在线离线，即驱动sdk与实际设备的在线离线，可以是tcp通道，可以是串口连接等
	 * @param state
	 * @return
	 */
	@Deprecated
	public ResultDTO online(boolean state) throws Exception {
		String[] devIdTmp = new String[]{zm.getUuid() + "." + getSysId()};
		if(state) {
			return zm.DevOnline(devIdTmp);
		} else {
			return zm.DevOffine(devIdTmp);
		}
	}

	/**
	 * 采集
	 * 后续会移除这个接口，所以禁止继承
	 * @param dataId
	 * @return List<DataPubDTO>
	 */
	@Deprecated()
	public final Object[] Collectig(String dataId) throws Exception {
		if(data2attrs.containsKey(dataId)) {
			Object dataObject = data2attrs.get(dataId);
			DataDTO dataDTO = JSONObject.toJavaObject((JSON) dataObject, DataDTO.class);
			return Collectig(dataDTO);
		}
		throw new DataNotExistError(dataId);
	}

	public abstract Object[] Collectig(final DataDTO dataDTO) throws InterruptedException;

	/**
	 * 控制，事件回调借口，其他操作访问
	 * @param fromUuid
	 * @param type
	 * @param data
	 * @return
	 */
	public abstract ResultDTO Event_customBroadcast(String fromUuid, String type,String data);
	public ResultDTO Event_customBroadcast(String fromUuid, CallbackTypeEnum type, String data) {
		return Event_customBroadcast(fromUuid, type.toString(), data);
	}

	/**
	 * 事件回调接口，监测点操作访问
	 * @param dataId
	 * @param condition
	 * @return
	 */
	@Deprecated
	public final ResultDTO Event_getData(String dataId, String condition) {
		return Event_getData(getDataDTO(dataId), condition);
	}
	public abstract ResultDTO Event_getData(DataDTO dataDTO, String condition);

	/**
	 * 事件回调接口，监测点操作访问
	 * @param dataId
	 * @param value
	 * @return
	 */
	@Deprecated
	public final ResultDTO Event_setData(String dataId, String value) {
		DataDTO dataDTO = getDataDTO(dataId);
		return Event_setData(dataDTO, value);
	}
	public abstract  ResultDTO Event_setData(DataDTO dataDTO, String value);

	/**
	 * 账户下订阅的所有监测点进行pub时，会到所有接入点的所有设备驱动中走一遍！这个时候是监测点全id了！
	 * @param point
	 * @param value
	 * @return
	 */

	public abstract ResultDTO Event_syncPubMsg(String point, String value);

}